﻿
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using EFCachingProvider.Caching;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sop;
using Sop.Collections.BTree;

namespace EFCachingProvider.Tests
{
    /// <summary>
    /// Scalable Cache Tests.
    /// 
    /// These tests were retrofit of the InMemoryCacheTests provided
    /// in EFCachingProvider.Tests project. It is a good set of unit tests
    /// that were found to be highly usable in ScalableCache unit testing.
    /// </summary>
    [TestClass]
    public class ScalableCacheTests
    {
        [TestMethod]
        public void ScalableCacheCacheExpirationTest1()
        {
            var cache = new ScalableCache.ScalableCache();

            object value;

            // current time is 10:00
            cache.GetCurrentDate = () => new DateTime(2009,1,1,10,0,0);

            // set expiration time to 11:00
            cache.PutItem("Item1", "someValue", new string[0], TimeSpan.Zero, new DateTime(2009, 1, 1, 11, 0, 0));

            // make sure the item is still there
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 10, 59, 59);
            Assert.IsTrue(cache.GetItem("Item1", out value));
            Assert.AreEqual(value, "someValue");

            // make sure the item gets evicted at 11:00
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 11, 00, 00);
            Assert.IsFalse(cache.GetItem("Item1", out value));
        }

        [TestMethod]
        public void ScalableCacheCacheExpirationTest2()
        {
            var cache = new ScalableCache.ScalableCache();

            object value;

            // current time is 10:00
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 10, 0, 0);

            // set expiration time to 1 hour from the last access
            cache.PutItem("Item1", "someValue", new string[0], TimeSpan.FromHours(1), DateTime.MaxValue);

            // make sure the item is still there
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 10, 59, 59);
            Assert.IsTrue(cache.GetItem("Item1", out value));
            Assert.AreEqual(value, "someValue");

            // make sure the item does not get evicted at 11:00 because we have touched it a second ago
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 11, 00, 00);
            Assert.IsTrue(cache.GetItem("Item1", out value));
            Assert.AreEqual(value, "someValue");

            // make sure the item gets evicted at 12:00 because we have touched it an hour ago
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 12, 00, 00);
            Assert.IsFalse(cache.GetItem("Item1", out value));
        }

        [TestMethod]
        public void ScalableCacheCacheExpirationTest3()
        {
            var cache = new ScalableCache.ScalableCache();

            object value;

            // current time is 10:00
            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 10, 0, 0);
            cache.PutItem("Item1", "someValue", new string[0], TimeSpan.Zero, new DateTime(2009, 1, 1, 10, 0, 0));
            cache.PutItem("Item2", "someValue", new string[0], TimeSpan.Zero, new DateTime(2009, 1, 1, 10, 1, 0));
            cache.PutItem("Item3", "someValue", new string[0], TimeSpan.Zero, new DateTime(2009, 1, 1, 10, 2, 0));
            cache.PutItem("Item4", "someValue", new string[0], TimeSpan.Zero, new DateTime(2009, 1, 1, 10, 3, 0));

            cache.GetCurrentDate = () => new DateTime(2009, 1, 1, 10, 2, 0);

            Assert.IsFalse(cache.GetItem("Item1", out value));
            Assert.IsFalse(cache.GetItem("Item2", out value));
            Assert.IsFalse(cache.GetItem("Item3", out value));
            Assert.IsTrue(cache.GetItem("Item4", out value));
        }

        [TestMethod]
        public void DependenciesTest()
        {
            var cache = new ScalableCache.ScalableCache();

            cache.PutItem("A", "A1", new string[] { "set1" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("B", "B1", new string[] { "set1", "set2" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("C", "C1", new string[] { "set2", "set3" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("D", "D1", new string[] { "set3", "set1" }, TimeSpan.Zero, DateTime.MaxValue);

            cache.InvalidateSets(new string[] { "set2" });
            object value;
            Assert.IsTrue(cache.GetItem("A", out value));
            Assert.IsTrue(cache.GetItem("D", out value));
            Assert.IsFalse(cache.GetItem("C", out value));
            Assert.IsFalse(cache.GetItem("B", out value));
        }

        [TestMethod]
        public void DependenciesTest2()
        {
            var cache = new ScalableCache.ScalableCache();

            cache.PutItem("A", "A1", new string[] { "set1" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("B", "B1", new string[] { "set1", "set2" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("C", "C1", new string[] { "set2", "set3" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("D", "D1", new string[] { "set3", "set1" }, TimeSpan.Zero, DateTime.MaxValue);

            cache.InvalidateSets(new string[] { "set1" });
            object value;
            Assert.IsTrue(cache.GetItem("C", out value));
            Assert.IsFalse(cache.GetItem("A", out value));
            Assert.IsFalse(cache.GetItem("D", out value));
            Assert.IsFalse(cache.GetItem("B", out value));
        }

        [TestMethod]
        public void DependenciesTest3()
        {
            var cache = new ScalableCache.ScalableCache();

            cache.PutItem("A", "A1", new string[] { "set1" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("B", "B1", new string[] { "set1", "set2" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("C", "C1", new string[] { "set2", "set3" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("D", "D1", new string[] { "set3", "set1" }, TimeSpan.Zero, DateTime.MaxValue);

            cache.InvalidateSets(new string[] { "set77" });
            object value;
            Assert.IsTrue(cache.GetItem("C", out value));
            Assert.IsTrue(cache.GetItem("A", out value));
            Assert.IsTrue(cache.GetItem("D", out value));
            Assert.IsTrue(cache.GetItem("B", out value));
        }

        [TestMethod]
        public void DependenciesTest4()
        {
            var cache = new ScalableCache.ScalableCache();

            cache.PutItem("A", "A1", new string[] { "set1" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("B", "B1", new string[] { "set1", "set2" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("C", "C1", new string[] { "set2", "set3" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("D", "D1", new string[] { "set3", "set1" }, TimeSpan.Zero, DateTime.MaxValue);
            cache.PutItem("B", "C1", new string[] { "set3" }, TimeSpan.Zero, DateTime.MaxValue);

            cache.InvalidateSets(new string[] { "set3" });
            object value;
            Assert.IsFalse(cache.GetItem("C", out value));
            Assert.IsTrue(cache.GetItem("A", out value));
            Assert.IsFalse(cache.GetItem("D", out value));
            Assert.IsFalse(cache.GetItem("B", out value));
        }

        [TestMethod]
        public void SynchronizerTests()
        {
            var synch = new Synchronizer();
            synch.Lock(OperationType.Read);
            synch.Lock(OperationType.Read);
            synch.Unlock(OperationType.Read);
            synch.Unlock(OperationType.Read);
        }
        [TestMethod]
        public void SynchronizerWriteTests()
        {
            var synch = new Synchronizer();
            synch.Lock(OperationType.Write);
            synch.Lock(OperationType.Write);
            synch.Unlock(OperationType.Write);
            synch.Unlock(OperationType.Write);
        }

        [TestMethod]
        public void MicroStressTest()
        {
            // run a bunch of concurrent reads and writes, make sure we get no exceptions

            // Set Store Pooling Count to 26 as this stress test uses 26 maxed Dependent Entity sets, they will all be stored opened in Store Pool by SOP.
            Profile.MaxStoreInstancePoolCount = 26;

            Sop.Log.Logger.DefaultLogDirectory = "c:\\SopBin";
            var cache = new ScalableCache.ScalableCache(storeFilename:"c:\\SopBin\\CacheStore.dta");

            int numberOfRequestBatches = 50; // will be multiplied by 5 (3 readers + 1 writer + 1 invalidator)
            int numberOfIterationsPerThread = 50000;

            ManualResetEvent startEvent = new ManualResetEvent(false);

            Action writer = () =>
                {
                    startEvent.WaitOne();
                    Random random = new Random();

                    for (int i = 0; i < numberOfIterationsPerThread; ++i)
                    {
                        string randomKey = Guid.NewGuid().ToString("N").Substring(0, 4);
                        string randomValue = randomKey + "_V";
                        List<string> dependentSets = new List<string>();
                        int numberOfDependencies = random.Next(5);
                        for (int j = 0; j < numberOfDependencies; ++j)
                        {
                            string randomSetName = new string((char) ('A' + random.Next(26)), 1);
                            dependentSets.Add(randomSetName);
                        }
                        cache.PutItem(randomKey, randomValue, dependentSets, TimeSpan.FromSeconds(35), DateTime.MaxValue);
                    }
                };

            Action invalidator = () =>
                {
                    startEvent.WaitOne();
                    Random random = new Random();

                    for (int i = 0; i < numberOfIterationsPerThread; ++i)
                    {
                        List<string> dependentSets = new List<string>();
                        int numberOfDependencies = random.Next(5);
                        for (int j = 0; j < numberOfDependencies; ++j)
                        {
                            string randomSetName = new string((char) ('A' + random.Next(26)), 1);
                            dependentSets.Add(randomSetName);
                        }

                        cache.InvalidateSets(dependentSets);
                    }
                };

            Action reader = () =>
                {
                    startEvent.WaitOne();
                    Random random = new Random();

                    for (int i = 0; i < numberOfIterationsPerThread; ++i)
                    {
                        string randomKey = Guid.NewGuid().ToString("N").Substring(0, 4);
                        object value;

                        if (cache.GetItem(randomKey, out value))
                        {
                            Assert.AreEqual(randomKey + "_V", value);
                        }
                    }
                };

            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < numberOfRequestBatches; ++i)
            {
                threads.Add(new Thread(() => writer()));
                threads.Add(new Thread(() => invalidator()));
                threads.Add(new Thread(() => reader()));
                threads.Add(new Thread(() => reader()));
                threads.Add(new Thread(() => reader()));
            }

            foreach (Thread t in threads)
            {
                t.Start();
            }

            startEvent.Set();

            foreach (Thread t in threads)
            {
                t.Join();
            }

            // commit Scalable Cache transaction.
            cache.Server.Commit();
        }
    }
}
